﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Nature.Data.CkMarkup;

namespace Nature.Data
{

    public interface IContractsHeap
    {
        System.Collections.IEnumerable ToEntitiesHeap(ModelSetupInfo filter);
    }

    public class ModelFilters
    {
        public Func<IChemicalElement, bool> ChemicalElementFilter { get; set; }

        public Func<ChemicalSpecies, bool> ChemicalSpeciesFilter { get; set; }

        internal System.Collections.IEnumerable ReduceEntitiesSet(System.Collections.IEnumerable entitiesHeap)
        {
            var includeElement = this.ChemicalElementFilter;
            var includeSpecies = this.ChemicalSpeciesFilter;
            if (includeElement == null &&
                includeSpecies == null) { return entitiesHeap; }
            List<object> entities = new List<object>();
            entities.AddRange(entitiesHeap.Cast<object>());

            var filteredElements = entities.OfType<ChemicalElement>().ToList();
            if (includeElement != null) { filteredElements = filteredElements.Where(el => includeElement(el) == true).ToList(); }
            var filteredSpecies =
                (
                    from el in filteredElements
                    from af in el.AtomsPerMoleculeAmounts
                    where af.Species.AtomsPerMoleculeAmounts.All(i => filteredElements.Contains(i.Element))
                    select af.Species
                ).Distinct()
                .ToList();
            if (includeSpecies != null)
            {
                filteredSpecies = filteredSpecies.Where(sp => includeSpecies(sp) == true).ToList();
            }
            entities.Clear();
            entities.AddRange(
                (from sp in filteredSpecies
                 from af in sp.AtomsPerMoleculeAmounts
                 select af).Distinct()
            );
            entities.AddRange(
                (from sp in filteredSpecies
                 from af in sp.AtomsPerMoleculeAmounts
                 select af.Element).Distinct()
            );
            entities.AddRange(filteredSpecies);
            entities.AddRange(
                    from sp in filteredSpecies
                    select sp.Thermo7
                );
            entities.AddRange(
                    from sp in filteredSpecies
                    select sp.GasKinParameters
                );
            return entities;
        }
    }

    public class ModelSetupInfo : ModelFilters
    {          
        public readonly SpeciesDataFallBackQueue<Thermo7> Thermo7FallBackQueue = new SpeciesDataFallBackQueue<Thermo7>();

        public readonly SpeciesDataFallBackQueue<GasKinParameters> GasKinParametersFallBackQueue = new SpeciesDataFallBackQueue<GasKinParameters>();        
    }


    


    public class DataBag : Collection<object>, IContractsHeap
    {        
        public System.Collections.IEnumerable ToEntitiesHeap(ModelSetupInfo filter)
        {
            List<object> entities = new List<object>();
            var readOnlyEntities = new ReadOnlyCollection<object>(entities);

            var apm = this.OfType<AtomsPerMoleculeAmount>();                  
            entities.AddRange( apm.ToList() );

            var elements =
                (from a in entities.OfType<AtomsPerMoleculeAmount>()
                group a by a.ElementID into grp
                select new ChemicalElement 
                {
                    ElementID = grp.Key,
                    ElementName = ChemicalElementMass.GetNameByElementID(grp.Key),
                    ElementMolarMass = ChemicalElementMass.GetMassByElementID(grp.Key),
                    AtomsPerMoleculeAmounts = grp.ToArray()
                }).ToList();
            elements.ForEach(i => {
                foreach (var a in i.AtomsPerMoleculeAmounts) { a.Element = i; }
            });
            entities.AddRange(elements.ToList());

            var species =
                (from a in entities.OfType<AtomsPerMoleculeAmount>()
                group a by a.SpeciesID into grp
                select new ChemicalSpecies 
                {
                    SpeciesID = grp.Key,
                    AtomsPerMoleculeAmounts = grp.ToArray(),
                    SpeciesMolarMass = grp.Sum(i=> i.AtomsPerMolecule * i.Element.ElementMolarMass)
                }).ToList();
            entities.AddRange(species);
            species.ForEach(currSpecies =>
            {
                foreach (var a in currSpecies.AtomsPerMoleculeAmounts) { a.Species = currSpecies; }
                var thermo7 =
                    (from th7 in this.OfType<IThermo7>().Where(t => t.SpeciesID == currSpecies.SpeciesID)
                    select new Thermo7 
                    {
                        SpeciesID = currSpecies.SpeciesID,
                        Species = currSpecies,
                        Ranges = th7.Ranges.OrderBy(r => r.Tmin).ToArray()
                    }).SingleOrDefault();
                if (thermo7 == null)
                {
                    thermo7 = filter.Thermo7FallBackQueue.Construct(currSpecies.SpeciesID, readOnlyEntities);
                    thermo7.Species = currSpecies;
                }
                var gasKinParams =
                    (
                        from gkp in this.OfType<IGasKinParameters>().Where(i => i.SpeciesID == currSpecies.SpeciesID)
                        select new GasKinParameters(gkp) 
                        {
                            Species = currSpecies
                        }
                    ).SingleOrDefault();
                if (gasKinParams == null)
                {
                    gasKinParams = filter.GasKinParametersFallBackQueue.Construct(currSpecies.SpeciesID, readOnlyEntities);
                    gasKinParams.Species = currSpecies;
                }
                currSpecies.Thermo7 = thermo7;
                currSpecies.GasKinParameters = gasKinParams;
                entities.Add(thermo7);
                entities.Add(gasKinParams);
            });
            return filter.ReduceEntitiesSet(entities);
        }
    }


    public sealed class CkDataBag : DataBag
    {
        public CkDataBag(params Uri[] ckMarkupResources)
        {
            var tasks =
                (from uri in ckMarkupResources
                 where uri != null
                 select Task.Factory.StartNew<CkTextFragment>(() =>
                 {
                     var webClient = new WebClient();
                     return Regex.IsMatch(uri.AbsolutePath, "(?xis)tran")
                     ? (CkTextFragment)CkTransportSet.Parse((new WebClient()).DownloadString(uri))
                     : (CkTextFragment)CkScript.Parse((new WebClient()).DownloadString(uri));
                 })).ToList();

            foreach (var task in tasks)
            {
                base.Add(task.Result);
                var script = task.Result as CkScript;
                var transport = task.Result as CkTransportSet;
                if (script != null)
                {
                    foreach (var apm in
                            from set in script.ThermoSetsCollection
                            from apm in set.AtomsPerMoleculeAmounts
                            select apm)
                    {
                        base.Add(apm);
                    }
                    foreach( var thermo7Contract in
                            from set in script.ThermoSetsCollection
                            from th7 in set.Thermo7Contracts
                            select th7)
                    {
                        base.Add(thermo7Contract);
                    }                    
                }
                else if (transport != null)
                {
                    foreach(var gkp in transport.Items)
                    {
                        base.Add(gkp);
                    }
                }
            }
        }
    }

}
